Return to start page
Core/General/Library Unit.j
1 library ALibraryCoreGeneralUnit requires ALibraryCoreGeneralItem
2
3 /**
4 * Sicheres Iterieren per FirstOfGroup
5 *
6 * Heyhey,
7 *
8 * dieser Bug (oder Feature, je nachdem wie mans nimmt) hat mich etwa 4 Stunden debuggen
9 * gekostet.
10 *
11 * Hintergrund: Wie ihr wisst kann man ja über eine Unitgroup iterieren (und diese dabei leeren)
12 * (Die Gruppe nenne ich einfach mal g):
13 *
14 * @code
15 * local unit u
16 * loop
17 * set u = FirstOfGroup(g)
18 * exitwhen u == null
19 *
20 * //HIER das mit der unit machen was man machen will
21 *
22 * call GroupRemoveUnit(g,u)
23 * endloop
24 * @endcode
25 *
26 * Tja, nur blöd dass das buggy ist, sobald einmal eine unit in der Gruppe aus dem Spiel
27 * entfernt wurde ohne sie vorher ordnungsgemäß aus der Gruppe zu removen. Das wusste ich bisher
28 * auch nicht.
29 *
30 * Beispiel:
31 * Wir adden in eine Gruppe 4 units. Dann entfernen wir die erste geaddete unit per
32 * RemoveUnit(...) aus dem Spiel. Nun ist die Gruppe kaputt (zumindest für das Iterieren per
33 * FirstOfGroup()). Denn FirstOfGroup(g) wird nun null liefern, die schleife endet also sofort
34 * obwohl drei units in der Group sind. Denn die "Lücke" die die unit hinterlassen hat bleibt in
35 * der Group. Und das blödeste: Diese lücke können wir auch nicht mehr per call
36 * GroupRemoveUnit(FirstOfGroup()) entfernen, da ja FoG nun null zurückliefert und
37 * GroupRemoveUnit(null) nichts macht.
38 *
39 *
40 * ALSO VORSICHT! Das FirstOfGroup(...) null zurückliefert heisst nicht unbedingt, dass die
41 * Gruppe leer ist!
42 *
43 * Deshalb hab ich mir eine kleine Funktion geschrieben die äquivalent zu einem Aufruf von
44 * FirstOfGroup ist, aber solche Lücken erkennt und die Gruppe automatisch repariert sobald sie
45 * auf so eine Lücke trifft, hier ist sie:
46 *
47 * Die Idee:
48 * Erstmal führt die Funktion ein normales FirstOfGroup auf die gewünschte Gruppe aus. Liefert
49 * dieser aufruf NICHT null, klappt ja alles perfekt, dann gibt sie einfach die unit zurück.
50 * Liefert der Aufruf aber null können zwei Fälle eingetreten sein:
51 *
52 * 1.) Die Group ist tatsächlich empty
53 * 2.) Wir sind auf eine Lücke gestoßen die repariert werden muss.
54 *
55 * Das überprüfen wir einfach indem wir ein GroupIsEmpty-derivat aufrufen. Liefert es true
56 * zurück war die gruppe wirklich leer und wir können null returnen. sonst reparieren wir sie,
57 * das geht so:
58 * Wir kopieren per ForGroup alle units in eine swap gruppe. Dabei werden lücken nicht
59 * mitkopiert, da ForGroup so schlau ist und solche lücken beim iterieren nicht beachtet. Dann
60 * leeren wir die gruppe und kopieren einfach die units aus der swapgruppe zurück und fertig ist
61 * die reparierte Gruppe auf die wir nun ein erneutes FirstOfGroup aufrufen um die wirkliche
62 * first unit zu ermitteln die wir zurück geben.
63 *
64 * Ich empfehle euch dringend diese Funktion zu benutzen wenn ihr über eine Group per
65 * FirstOfGroup iterieren wollt, aber nicht sicher sein könnt, dass keine unit jemals aus dem
66 * Spiel entfernt wurde (was auch nach dem normalen tod und dekay automatisch passiert) die in
67 * der Gruppe war.
68 *
69 * Hoffe ich kann euch damit die Stunden des Bugfixen die ich hatte ersparen...
70 * @author gexxo
71 * @param g Used unit group.
72 * @return Returns the first unit of group g. If the group is empty it will return null.
73 */
74 function FirstOfGroupSave takes group g returns unit
75 local unit u = FirstOfGroup(g) //Try a normal first of group
76 local group swap
77
78 // If the result is null there may be gaps in the group
79 if u == null then
80 //Check if the group is empty. If it is not, then there must be gaps -> repair
81 set bj_isUnitGroupEmptyResult = true
82 call ForGroup(g, function IsUnitGroupEmptyBJEnum)
83 if not bj_isUnitGroupEmptyResult then
84 //** Repair the group **
85 set swap = CreateGroup()
86 call GroupAddGroup(g,swap) //Add all units to a swapping group
87 call GroupClear(g) //Clear the buggy group hence removing the gaps
88 call GroupAddGroup(swap,g) //Put the units back in from the swapping group
89
90 //Collect garbage
91 call DestroyGroup(swap)
92 set swap = null
93
94 //Do another FirstOfGroup to gain the real first unit
95 set u = FirstOfGroup(g)
96 endif
97 endif
98
99 return u //Return the unit we wanted
100 endfunction
101
102 /// @author Tamino Dauth
103 /// @todo Probably bugged, do not use.
104 function MoveItemToSlot takes unit usedUnit, item usedItem, integer slot returns boolean
105 return IssueTargetOrderById(usedUnit, 852002 + slot, usedItem)
106 endfunction
107
108 /// @author Tamino Dauth
109 /// @todo Probably bugged, do not use.
110 function UseItemOfSlot takes unit usedUnit, integer slot returns boolean
111 return IssueImmediateOrderById(usedUnit, 852008 + slot)
112 endfunction
113
114 /// @author Tamino Dauth
115 /// @todo Probably bugged, do not use.
116 function UseItemOfSlotOnTarget takes unit usedUnit, integer slot, widget target returns boolean
117 return IssueTargetOrderById(usedUnit, 852008 + slot, target)
118 endfunction
119
120 /// @author Tamino Dauth
121 /// @todo Probably bugged, do not use.
122 function UseItemOfSlotOnPoint takes unit usedUnit, integer slot, real x, real y returns boolean
123 return IssuePointOrderById(usedUnit, 852008 + slot, x, y)
124 endfunction
125
126 /// @author Tamino Dauth
127 function UnitDropSlot takes unit usedUnit, integer slot0, integer slot1 returns boolean
128 local item slotItem = UnitItemInSlot(usedUnit, slot0)
129 local boolean result = UnitDropItemSlot(usedUnit, slotItem, slot1)
130 set slotItem = null
131 return result
132 endfunction
133
134 /// @author Tamino Dauth
135 /// @return Missing life of unit @param usedUnit.
136 function GetUnitMissingLife takes unit usedUnit returns real
137 return GetUnitState(usedUnit, UNIT_STATE_MAX_LIFE) - GetUnitState(usedUnit, UNIT_STATE_LIFE)
138 endfunction
139
140 /// @author Tamino Dauth
141 /// @return Missing life of unit @param usedUnit in percent.
142 function GetUnitMissingLifePercent takes unit usedUnit returns real
143 return 100.0 - GetUnitStatePercent(usedUnit, UNIT_STATE_LIFE, UNIT_STATE_MAX_LIFE)
144 endfunction
145
146 /// @author Tamino Dauth
147 /// @return Missing mana of unit @param usedUnit.
148 function GetUnitMissingMana takes unit usedUnit returns real
149 return GetUnitState(usedUnit, UNIT_STATE_MAX_MANA) - GetUnitState(usedUnit, UNIT_STATE_MANA)
150 endfunction
151
152 /// @author Tamino Dauth
153 /// @return Missing mana of unit @param usedUnit in percent.
154 function GetUnitMissingManaPercent takes unit usedUnit returns real
155 return 100.0 - GetUnitStatePercent(usedUnit, UNIT_STATE_MANA, UNIT_STATE_MAX_MANA)
156 endfunction
157
158 /// @author Tamino Dauth
159 /// @todo Finish
160 function CopyUnit takes unit usedUnit, real x, real y, real facing, integer stateMethod returns unit
161 local player owner = GetOwningPlayer(usedUnit)
162 local unit result = CreateUnit(owner, GetUnitTypeId(usedUnit), x, y, facing)
163 local real oldRatio
164 local player user
165 local integer i
166 local item oldSlotItem
167 local item newSlotItem
168 call ShowUnit(result, IsUnitHidden(usedUnit))
169 call PauseUnit(result, IsUnitPaused(usedUnit))
170 call SetResourceAmount(result, GetResourceAmount(usedUnit))
171 call SetUnitUserData(result, GetUnitUserData(usedUnit))
172 // Set the unit's life and mana according to the requested method.
173 if (stateMethod == bj_UNIT_STATE_METHOD_RELATIVE) then
174 // Set the replacement's current/max life ratio to that of the old unit.
175 // If both units have mana, do the same for mana.
176 if (GetUnitState(usedUnit, UNIT_STATE_MAX_LIFE) > 0) then
177 set oldRatio = GetUnitState(usedUnit, UNIT_STATE_LIFE) / GetUnitState(usedUnit, UNIT_STATE_MAX_LIFE)
178 call SetUnitState(result, UNIT_STATE_LIFE, oldRatio * GetUnitState(result, UNIT_STATE_MAX_LIFE))
179 endif
180 if (GetUnitState(usedUnit, UNIT_STATE_MAX_MANA) > 0) and (GetUnitState(result, UNIT_STATE_MAX_MANA) > 0) then
181 set oldRatio = GetUnitState(usedUnit, UNIT_STATE_MANA) / GetUnitState(usedUnit, UNIT_STATE_MAX_MANA)
182 call SetUnitState(result, UNIT_STATE_MANA, oldRatio * GetUnitState(result, UNIT_STATE_MAX_MANA))
183 endif
184 elseif (stateMethod == bj_UNIT_STATE_METHOD_ABSOLUTE) then
185 // Set the replacement's current life to that of the old unit.
186 // If the new unit has mana, do the same for mana.
187 call SetUnitState(result, UNIT_STATE_LIFE, GetUnitState(usedUnit, UNIT_STATE_LIFE))
188 if (GetUnitState(result, UNIT_STATE_MAX_MANA) > 0) then
189 call SetUnitState(result, UNIT_STATE_MANA, GetUnitState(usedUnit, UNIT_STATE_MANA))
190 endif
191 elseif (stateMethod == bj_UNIT_STATE_METHOD_DEFAULTS) then
192 // The newly created unit should already have default life and mana.
193 elseif (stateMethod == bj_UNIT_STATE_METHOD_MAXIMUM) then
194 // Use max life and mana.
195 call SetUnitState(result, UNIT_STATE_LIFE, GetUnitState(result, UNIT_STATE_MAX_LIFE))
196 call SetUnitState(result, UNIT_STATE_MANA, GetUnitState(result, UNIT_STATE_MAX_MANA))
197 endif
198
199 set i = 0
200 loop
201 exitwhen (i == bj_MAX_PLAYERS)
202 set user = Player(i)
203 if (IsUnitSelected(usedUnit, user)) then
204 call SelectUnitAddForPlayer(result, user)
205 endif
206 set user = null
207 set i = i + 1
208 endloop
209
210 set i = 0
211 loop
212 exitwhen (i == bj_MAX_INVENTORY)
213 set oldSlotItem = UnitItemInSlot(usedUnit, i)
214 if (oldSlotItem != null) then
215 set newSlotItem = CopyItem(oldSlotItem, GetUnitX(result), GetUnitY(result))
216 call UnitAddItem(result, newSlotItem) /// @todo add to old slot
217 set oldSlotItem = null
218 endif
219 set i = i + 1
220 endloop
221
222 if (IsUnitType(usedUnit, UNIT_TYPE_HERO) and IsUnitType(result, UNIT_TYPE_HERO)) then
223 call SetHeroXP(result, GetHeroXP(usedUnit), false)
224 endif
225
226 return result
227 endfunction
228
229 /// @author Tamino Dauth
230 function CreateUnitsAtPoint takes integer count, integer unitTypeId, player whichPlayer, real x, real y, real face returns group
231 local group unitGroup = CreateGroup()
232 local unit groupMember
233 loop
234 set count = count - 1
235 exitwhen (count < 0)
236 set groupMember = CreateUnit(whichPlayer, unitTypeId, x, y, face)
237 call GroupAddUnit(unitGroup, groupMember)
238 endloop
239 return unitGroup
240 endfunction
241
242 /// @author Tamino Dauth
243 function CreateUnitAtRect takes player whichPlayer, integer unitTypeId, rect whichRect, real facing returns unit
244 return CreateUnit(whichPlayer, unitTypeId, GetRectCenterX(whichRect), GetRectCenterY(whichRect), facing)
245 endfunction
246
247 /**
248 * Doesn't pause or unpause already paused units.
249 * Doesn't try to unpause new created units.
250 * Doesn't use global variable.
251 * Saves a key on each unit to identify paused unit.
252 * Key is removed automatically when unit is removed from game.
253 * @see PauseAllUnitsBJ
254 * @author Tamino Dauth
255 */
256 function PauseAllUnits takes boolean pause returns nothing
257 local integer i
258 local player whichPlayer
259 local group whichGroup
260 local AGroup unitGroup
261 set whichGroup = CreateGroup()
262 set unitGroup = AGroup.create()
263 set i = 0
264 loop
265 exitwhen (i == bj_MAX_PLAYER_SLOTS)
266 set whichPlayer = Player(i)
267 // If this is a computer slot, pause/resume the AI.
268 if (GetPlayerController(whichPlayer) == MAP_CONTROL_COMPUTER) then
269 call PauseCompAI(whichPlayer, pause)
270 endif
271 // Enumerate and unpause every unit owned by the player.
272 call GroupEnumUnitsOfPlayer(whichGroup, whichPlayer, null)
273 call unitGroup.addGroup(whichGroup, false, true)
274 set i = i + 1
275 endloop
276 call DestroyGroup(whichGroup)
277 set whichGroup = null
278 set i = 0
279 loop
280 exitwhen (i == unitGroup.units().size())
281 if (pause and not IsUnitPaused(unitGroup.units()[i])) then
282 call PauseUnit(unitGroup.units()[i], true)
283 call AHashTable.global().setHandleBoolean(unitGroup.units()[i], "PauseAllUnits:IsPaused", true)
284 elseif (not pause and AHashTable.global().hasHandleBoolean(unitGroup.units()[i], "PauseAllUnits:IsPaused")) then
285 call PauseUnit(unitGroup.units()[i], false)
286 call AHashTable.global().removeHandleBoolean(unitGroup.units()[i], "PauseAllUnits:IsPaused")
287 endif
288 set i = i + 1
289 endloop
290 call unitGroup.destroy()
291 endfunction
292
293 private function hookFunctionRemoveUnit takes unit whichUnit returns nothing
294 if (AHashTable.global().hasHandleBoolean(whichUnit, "PauseAllUnits:IsPaused")) then
295 call AHashTable.global().removeHandleBoolean(whichUnit, "PauseAllUnits:IsPaused")
296 endif
297 endfunction
298
299 hook RemoveUnit hookFunctionRemoveUnit
300
301 endlibrary